package org.infodavid.common.impl.services;

import java.io.IOException;
import java.security.Principal;
import java.security.acl.Group;
import java.util.ArrayList;
import java.util.List;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.security.SimpleGroup;
import org.jboss.security.auth.spi.UsernamePasswordLoginModule;

import org.infodavid.common.dto.IUserGroupListItem.EUserRole;
import org.infodavid.common.model.IUser;
import org.infodavid.common.persistence.UserDataService;
import org.infodavid.common.services.IApplicationContext;
import org.infodavid.common.services.exceptions.ServiceException;

/**
 * The Class AbstractLoginModule.
 */
public abstract class AbstractLoginModule extends UsernamePasswordLoginModule {

	/**
	 * The Class LoginHandler.
	 */
	public static class LoginHandler implements CallbackHandler {

		/** The login. */
		private final String login;

		/** The password. */
		private final char[] password;

		/**
		 * Instantiates a new login handler.
		 * @param pLogin the login
		 * @param pPassword the password
		 */
		public LoginHandler(final String pLogin, final String pPassword) {
			login = pLogin;
			password = pPassword.toCharArray();
		}

		/*
		 * See super class or interface. (non-Javadoc)
		 * @see
		 * javax.security.auth.callback.CallbackHandler#handle(javax.security.auth.callback.Callback[])
		 */
		public void handle(final Callback[] callbacks) throws IOException, UnsupportedCallbackException {
			NameCallback nc;
			PasswordCallback pc;

			for (int i = 0; i < callbacks.length; i++) {
				if (callbacks[i] instanceof NameCallback) {
					nc = (NameCallback)callbacks[i];

					nc.setName(login);
				}
				else if (callbacks[i] instanceof PasswordCallback) {
					pc = (PasswordCallback)callbacks[i];

					pc.setPassword(password);
				}
				else {
					throw new UnsupportedCallbackException(callbacks[i], "Wrong callback");
				}
			}
		}
	}

	/** The Constant log. */
	private static final Log LOGGER = LogFactory.getLog(AbstractLoginModule.class);

	/**
	 * Gets the application context.
	 * @return the application context
	 * @throws ServiceException the service exception
	 */
	protected abstract IApplicationContext getApplicationContext() throws ServiceException;

	/**
	 * Convert raw password.
	 * @param rawPassword the raw password
	 * @return the string
	 */
	protected String convertRawPassword(final String rawPassword) {
		return rawPassword;
	}

	/**
	 * Handle.
	 * @param error the error
	 * @throws LoginException the login exception
	 */
	protected void handle(final Throwable error) throws LoginException {
		if (error instanceof LoginException) {
			throw (LoginException)error;
		}

		LOGGER.error(error.getMessage(), error);

		throw new LoginException(error.getMessage());
	}

	/*
	 * See super class or interface. (non-Javadoc)
	 * @see org.jboss.security.auth.spi.UsernamePasswordLoginModule#getUsersPassword()
	 */
	@Override
	protected String getUsersPassword() throws LoginException {
		final IApplicationContext context = getApplicationContext();
		final String username = getUsername();
		String result = null;
		IUser user;

		try {
			user = UserDataService.getInstance().findByName(context.getPersistenceSession(), username);

			if (user == null) {
				LOGGER.debug("No user found for the given name:" + username);
			}
			else {
				result = convertRawPassword(user.getPassword());
			}
		}
		catch (final Exception e) {
			handle(e);
		}

		return result;
	}

	/*
	 * See super class or interface. (non-Javadoc)
	 * @see org.jboss.security.auth.spi.AbstractServerLoginModule#getRoleSets()
	 */
	@Override
	protected Group[] getRoleSets() throws LoginException {
		final IApplicationContext context = getApplicationContext();
		final UserDataService service = UserDataService.getInstance();
		final String username = getUsername();
		Group[] results = null;
		IUser user;

		try {
			user = service.findByName(context.getPersistenceSession(), username);

			if (user == null) {
				LOGGER.debug("No user found for the given name:" + username);
			}
			else {
				final List<EUserRole> roles =
				    service.findRoles(context.getPersistenceSession(), user.getKey());

				if (roles.isEmpty()) {
					LOGGER.debug("No role found for the given user:" + username);
				}
				else {
					results = new Group[roles.size()];
					final List<IUser> users = new ArrayList<IUser>();
					Group group;
					int i = 0;

					for (final EUserRole role : roles) {
						group = new SimpleGroup(role.name());

						service.findByRole(context.getPersistenceSession(), role, users, null);

						for (final Principal principal : users) {
							group.addMember(principal);
						}

						results[i++] = group;
						users.clear();
					}
				}
			}
		}
		catch (final Exception e) {
			handle(e);
		}

		return results;
	}
}
